#include "3DSLoader.h"
#include <stdexcept>
#include <cmath>
#include "3DSID.h"

using namespace std;

M3DSLoader::M3DSLoader() : NowPos( 0 ),FileLength( 0 ), Version( 0 )
{}
// Function		: open 3DS file.
// Description	: use locale::global to make sure we can read file from the path.
// Input		: 3DS file's path.
// Output		: nothing.
void M3DSLoader::OpenFile( const string& FileRoad )
{
	locale::global(locale(""));
	FileReader.open(FileRoad.c_str(), ifstream::binary);
	locale::global(locale("C"));
	if(!FileReader)
		throw std::runtime_error("Read 3DS file error.");
}
// Function		: close file reader.
// Description	: close file reader.
// Input		: nothing.
// Output		: nothing.
void M3DSLoader::CloseFile()
{
	FileReader.close();
}
// Function		: read 3DS file.
// Description	: read information from 3DS file, all other functions work for it.
// Input		: nothing.
// Output		: nothing.
void M3DSLoader::LoadFile()
{
	Chunk MyChunk;
	ReadChunk(&MyChunk);
	if(PRIMARY != MyChunk.ID)
		throw runtime_error("File format error.");
	FileLength = MyChunk.Len;

	ReadChunk(&MyChunk);
	ReadGLuint(&Version);
	while(NowPos < FileLength)
	{
		ReadChunk(&MyChunk);	
		if(MAINOBJECT == MyChunk.ID)
			LoadModel(MyChunk);
		else
			SkipNByte(MyChunk.Len - 6);
	}

	ComputeNormals();
}
// Function		: loading model.
// Description	: loading information of a model.
// Input		: chunk info of model
// Output		: nothing
void M3DSLoader::LoadModel(const Chunk& MyChunk)
{
	size_t BeginPos(NowPos - 6);
	Chunk TempChunk;
	while(NowPos - BeginPos != MyChunk.Len)
	{
		ReadChunk(&TempChunk);
		switch(TempChunk.ID)
		{
		case OBJECT:
			LoadObject(TempChunk);
			break;

		case MATERIAL:
			LoadMaterial(TempChunk);
			break;

		default:
			SkipNByte(TempChunk.Len - 6);
		}
	}
}
// Function		: loading objct.
// Description	: loading objct.
// Input		: chunk info of the object.
// Output		: nothing.
void M3DSLoader::LoadObject(const Chunk& MyChunk)
{
	Object object;
	object.Name = ReadString();
	MyModel.MyObject.push_back(object);

	Chunk ThisChunk;
	size_t BeginPos(NowPos - 7 - object.Name.size());
	while(NowPos - BeginPos != MyChunk.Len)
	{
		ReadChunk(&ThisChunk);
		if(OBJECT_MESH == ThisChunk.ID)
			LoadMesh(ThisChunk);
		else
			SkipNByte(ThisChunk.Len - 6);
	}
}
// Function		: loading mesh information.
// Description	: loading mesh information.
// Input		: chunk info of the mesh.
// Output		: nothing.
void M3DSLoader::LoadMesh(const Chunk& MyChunk)
{
	Object &object = MyModel.MyObject[MyModel.MyObject.size() - 1];

	size_t BeginPos(NowPos - 6);
	Chunk ThisChunk;
	while(NowPos - BeginPos != MyChunk.Len)
	{
		ReadChunk(&ThisChunk);
		switch(ThisChunk.ID)
		{
		case OBJECT_VERTICES:  //vertice
			LoadVertex( &object );
			break;

		case OBJECT_FACES:     //face
			LoadFaces( &object );
			break;

		case OBJECT_MATERIAL:  //material
			LoadObjectMaterial( &object );
			break;

		default:              //something we do not need
			SkipNByte( ThisChunk.Len - 6 );
		}
	}
}
// Function		: loading material information of object.
// Description	: loading material information of object.
// Input		: pointer of the object.
// Output		: nothing.
void M3DSLoader::LoadObjectMaterial(Object* object)
{
	string Name = ReadString();
	int Pos(-1);
	for(size_t i = 0; i != MyModel.MyMaterial.size(); ++ i)
	{
		if(MyModel.MyMaterial[i].name == Name)
			Pos = i;
	}

	if( Pos == -1 )
		throw runtime_error( "Search material failed." );

	GLushort Sum(0); GLushort FacePos(0);
	ReadGLushort(&Sum);
	for(size_t i = 0; i != Sum; ++ i)
	{
		ReadGLushort(&FacePos);
		object->Faces[FacePos].MaterialPos = Pos;
	}
}
// Function		: loading material information.
// Description	: loading material information.
// Input		: the chunk of material.
// Output		: nothing.
void M3DSLoader::LoadMaterial( const Chunk& MyChunk )
{
	Chunk TempChunk;
	Material material;
	size_t BeginPos(NowPos - 6);

	while(NowPos - BeginPos < MyChunk.Len)
	{
		ReadChunk(&TempChunk);
		switch(TempChunk.ID)
		{
		case MATNAME:                      //material name
			material.name = ReadString();
			break;
		case MAT_AMBIENT:                  //material Ambient
			LoadColor(material.ambientColor);
			break;
		case MAT_DIFFUSE:                  //material Diffuse
			LoadColor(material.diffuseColor);
			break;
		case MAT_SPECULAR:                 //material Specular
			LoadColor(material.specularColor);
			break;
		case MAT_SHININESS:                //material Shininess
			LoadPercent(&material.shininess);
			break;
		case MAT_TRANSPARENCY:             //material Transparency
			LoadPercent(&material.transparency);
			break;
		default:
			SkipNByte(TempChunk.Len - 6);
		}
	}
	MyModel.MyMaterial.push_back(material);
}
// Function		: loading color information.
// Description	: loading color information.
// Input		: the color pointer we should save to.
// Output		: nothing.
void M3DSLoader::LoadColor(float* color)
{
	Chunk TempChunk;
	ReadChunk(&TempChunk);
	switch( TempChunk.ID )
	{
	case COLOR_F:
		ReadGLfloat(&color[ 0 ]);
		ReadGLfloat(&color[ 1 ]);
		ReadGLfloat(&color[ 2 ]);
		break;
	case COLOR_24:
		GLubyte Byte;
		for(size_t i = 0; i != 3; ++i)
		{
			ReadGLubyte(&Byte);
			color[ i ] = Byte / 255.0;
		}
		break;
	default:
		SkipNByte(TempChunk.Len - 6);
	}
}
// Function		: loading Percentage.
// Description	: Shininess and transparency of the material is a percentage, 
//				  so we need a function to load them.
// Input		: where the percentage should be saved.
// Output		: nothing.
void M3DSLoader::LoadPercent(GLfloat* Temp)
{
	Chunk TempChunk;
	ReadChunk(&TempChunk);
	switch(TempChunk.ID)
	{
	case INT_PERCENTAGE:    //Int Percentage
		GLushort Percent;
		ReadGLushort(&Percent);
		*Temp = Percent / 100.0;
		break;
	case FLOAT_PERCENTAGE:  //Float Percentage
		ReadGLfloat(Temp);
		break;
	default:
		SkipNByte(TempChunk.Len - 6);
	}
}
// Function		: loading vector.
// Description	: loading vector.
// Input		: two vertices of a vector.
// Output		: vector.
Vertex M3DSLoader::Vectors(const Vertex& lPoint, const Vertex& rPoint)
{
	Vertex Point;
	Point.x = lPoint.x - rPoint.x;
	Point.y = lPoint.y - rPoint.y;
	Point.z = lPoint.z - rPoint.z;
	return Point;
}
// Function		: calculate the cross product of two vectors
// Description	: calculate the cross product of two vectors
// Input		: two vectors.
// Output		: vector.
Vertex M3DSLoader::Cross( const Vertex& lPoint, const Vertex& rPoint )
{
	Vertex Point;
	Point.x = lPoint.y * rPoint.z - lPoint.z * rPoint.y;
	Point.y = lPoint.z * rPoint.x - lPoint.x * rPoint.z;
	Point.z = lPoint.x * rPoint.y - lPoint.y * rPoint.x;
	return Point;
}
// Function		: unitise vector
// Description	: unitise vector
// Input		: vector
// Output		: unitised vector
void M3DSLoader::Normalize( Vertex* point )
{
	float Magnitude = sqrt(point->x * point->x + point->y * point->y + point->z * point->z);
	if(0 == Magnitude)
		Magnitude = 1;
	point->x /= Magnitude;				
	point->y /= Magnitude;				
	point->z /= Magnitude;											
}
// Function		: calculate normal vector for all planes.
// Description	: calculate normal vector for all planes.
// Input		: nothing.
// Output		: nothing.
void M3DSLoader::ComputeNormals()
{
	for(size_t i = 0; i != MyModel.MyObject.size(); ++ i)
	{
		Object& object = MyModel.MyObject[i];
		for(size_t j = 0; j != MyModel.MyObject[i].Faces.size(); ++j)
		{
			Face& face = object.Faces[j];
			const Vertex &Point1 = object.Vertexs[face.Index[0]];
			const Vertex &Point2 = object.Vertexs[face.Index[1]];
			const Vertex &Point3 = object.Vertexs[face.Index[2]];

			face.Normal = Cross(Vectors(Point1, Point3), Vectors(Point3, Point2));
			Normalize(&face.Normal);
		}
	}
}
// Function		: get the model we read.
// Description	: get the model we read.
// Input		: nothing.
// Output		: the model.
const Model& M3DSLoader::GetModel()
{
	return MyModel;
}
// Function		: load all faces.
// Description	: 3ds max face graphics are stored as triangular.
// Input		: 
// Output		:
void M3DSLoader::LoadFaces(Object* ThisObject)
{
	GLushort Sum(0);
	ReadGLushort(&Sum);
	Face face; GLushort Temp(0);
	for(size_t i = 0; i != Sum; ++i)
	{
		for(size_t j = 0; j != 4; ++j)
		{
			ReadGLushort(&Temp);
			if(j < 3)
				face.Index[j] = Temp;
		}
		ThisObject->Faces.push_back(face);
	}
}
// Function		: load Vertex.
// Description	: load Vertex.
// Input		: 
// Output		:
void M3DSLoader::LoadVertex( Object* const& ThisObject )
{
	GLushort Sum( 0 );
	ReadGLushort( &Sum );
	Vertex Point;
	float Num( 0 );float distence( 0 );
	for( size_t i = 0; i != Sum; ++ i )
	{

		ReadGLfloat( &Point.x );
		ReadGLfloat( &Point.z );
		ReadGLfloat( &Point.y );
		Point.z *= -1;
		ThisObject->Vertexs.push_back( Point );
	}
}

void M3DSLoader::ReadChunk( Chunk* MyChunk )
{
	ReadGLushort( &MyChunk->ID );
	ReadGLuint( &MyChunk->Len );
}

void M3DSLoader::ReadGLubyte( GLubyte* Ubyte )
{
	FileReader.read( reinterpret_cast< char* >( Ubyte ), sizeof( GLubyte ) );
	NowPos += sizeof( GLubyte );
}

void M3DSLoader::ReadGLushort( GLushort* Ushort )
{
	FileReader.read( reinterpret_cast< char* >( Ushort ), sizeof( GLushort ) );
	NowPos += sizeof( GLushort );
}

void M3DSLoader::ReadGLuint( GLuint* Uint )
{
	FileReader.read( reinterpret_cast< char* >( Uint ), sizeof( GLuint ) );
	NowPos += sizeof( GLuint );
}

void M3DSLoader::ReadGLfloat( GLfloat* Float )
{
	FileReader.read( reinterpret_cast< char* >( Float ), sizeof( GLfloat ) );
	NowPos += sizeof( GLfloat );
}

std::string M3DSLoader::ReadString()
{
	char alpha;	string TempWord;
	while( FileReader.get( alpha ), alpha != 0 )
		TempWord += alpha;
	NowPos += TempWord.size() + 1;
	return TempWord;
}

void M3DSLoader::SkipNByte( const size_t& Num )
{
	FileReader.seekg( Num, ifstream::cur );
	NowPos += Num;
}
